﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using NLog;
using Raytracer.Configurations;
using RaytracerLib;
using RaytracerLib.Layers;
using RaytracerLib.MathLib;
using RaytracerLib.Resamplers;
using RaytracerLib.Samplers;
using TomanuExtensions;
using System.Diagnostics;
using RaytracerLib.Renderers;
using TomanuExtensions.Utils;

namespace Raytracer.Tester
{
    public partial class TesterForm : Form
    {
        private Logger Logger = LogManager.GetLogger("Tester");
        private bool m_stopping = false;
        private bool m_testing = false;
        private List<int> Filter;
        private bool m_gen_diffs = true;

        private static ColorFloat[] m_errors = { ColorFloat.SRGB_Pink, ColorFloat.SRGB_Red,
                                                 ColorFloat.SRGB_Green, ColorFloat.SRGB_Green,
                                                 ColorFloat.SRGB_Blue, ColorFloat.SRGB_Blue,
                                                 ColorFloat.SRGB_Blue, ColorFloat.SRGB_Blue,
                                                 ColorFloat.Black };

        public TesterForm()
        {
            InitializeComponent();
        }

        private void event_TesterForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (m_testing)
            {
                e.Cancel = true;
                m_stopping = true;
            }
        }

        private bool CanAssert()
        {
#if DEBUG
            return true;
#else
            return false;
#endif
        }

        private bool CanTest()
        {
#if DEBUG
            return allowTestsInDebugCheckBox.Checked;
#else
            return System.Environment.Is64BitProcess && (!Debugger.IsAttached || allowTestsInDebugCheckBox.Checked);
#endif
        }

        private void event_TesterForm_Load(object sender, EventArgs e)
        {
            numbersTextBox.Text = Configuration.Instance.TestGeneratorNumbers;

            FormsUtils.CenterControlInPanel(centerPanel);

            UpdateButtons(CanTest(), CanAssert(), true);
        }

        private void UpdateButtons(bool a_enable_test, bool a_enable_debug, bool a_enable_rest)
        {
            speedTestButton.Enabled = a_enable_test;
            testAllImagesButton.Enabled = a_enable_test;
            testSelectedImagesButton.Enabled = a_enable_test;
            testAllErrors.Enabled = a_enable_test;
            testDebugAllImagesButton.Enabled = a_enable_debug;
            testDebugSelectedImagesButton.Enabled = a_enable_debug;
            allowTestsInDebugCheckBox.Enabled = a_enable_rest;
            checkBoxSkipBig.Enabled = a_enable_test;
        }

        private void event_numbersTextBox_TextChanged(object sender, EventArgs e)
        {
            Configuration.Instance.TestGeneratorNumbers = numbersTextBox.Text;
        }

        private void event_testSelectedAllButton_Click(object sender, EventArgs e)
        {
            Logger.Error("Testing selected all.");
            m_gen_diffs = true;
            TestFiles(true);
        }

        private void event_pictureBox_Paint(object sender, PaintEventArgs e)
        {
            centerPanel.PerformLayout();
        }

        private void event_testAllButton_Click(object sender, EventArgs e)
        {
            Logger.Error("Testing all images.");
            m_gen_diffs = true;
            TestFiles(false);
        }

        private void TestFiles(bool a_filter)
        {
            if (a_filter)
                Filter = Generator.GeneratorForm.GetFilter(numbersTextBox.Text);
            else
                Filter = null;

            Regex regex = new Regex("^\\d* - .*\\.xml");

            var temp = from file in PathResolver.TestScenesDir.GetFiles("*.xml", SearchOption.AllDirectories)
                       where regex.IsMatch(file.Name)
                       select new
                       {
                           file,
                           num = file.Name.Split(" - ").First().ToInt()
                       };

            var files = from gr in temp
                        where (Filter == null) || Filter.Contains(gr.num)
                        orderby gr.num, gr.file.Name
                        select gr.file;

            if (checkBoxSkipBig.Checked)
            {
                files = from file in files
                        where !file.FullName.EndsWith(" - big.xml")
                        select file;
            }

            TestFiles(files.ToArray());
        }

        private void TestFiles(FileInfo[] a_files)
        {
            try
            {
                testAllImagesButton.Enabled = false;
                testSelectedImagesButton.Enabled = false;
                numbersTextBox.Enabled = false;
                testAllErrors.Enabled = false;
                speedTestButton.Enabled = false;

                Task.Factory.StartNew(() =>
                {
                    m_testing = true;

                    try
                    {
                        int counter = 1;

                        bool err = false;
                        bool canceled = false;

                        foreach (var file in a_files)
                        {
                            if (m_stopping)
                            {
                                canceled = true;
                                break;
                            }

                            Logger.Info("{0} / {1} - {2}", counter, a_files.Count(),
                                file.FullName);

                            if (TestFile(file))
                            {
                                Logger.Error("Error");
                                err = true;
                            }

                            counter++;
                        }

                        if (canceled)
                            Logger.Error("Test was cancelled.");
                        else if (err)
                            Logger.Error("Errors");
                        else
                            Logger.Error(" All Ok");

                        m_testing = false;
                        Invoke(new Action(() => Close()));
                    }
                    catch (Exception ex)
                    {
                        Logger.Error(ex);
                    }
                    finally
                    {
                        m_testing = false;
                    }
                });
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                Close();
            }
        }

        private bool TestFile(FileInfo a_file)
        {
            Scene scene = Scene.LoadFromFile(a_file.FullName);

            var img_file = Path.GetDirectoryName(a_file.FullName) +
                Path.DirectorySeparatorChar +
                Path.GetFileNameWithoutExtension(a_file.FullName) + ".png";

            Renderer renderer = scene.CreateRenderer();

            pictureBox.Invoke(new Action(() =>
            {
                pictureBox.Image = new Bitmap(scene.ActiveCamera.Film.GetFilmRect().Width,
                    scene.ActiveCamera.Film.GetFilmRect().Height);
                pictureBox.Size = new Size(pictureBox.Image.Width, pictureBox.Image.Height);
            }));

            Generator.GeneratorForm.Repeatable(scene);

            renderer.ShouldStop += () => m_stopping;

            renderer.UpdateCallback += () =>
            {
                pictureBox.Invoke(new Action(() =>
                {
                    renderer.UpdateBitmap(pictureBox.Image as Bitmap);
                    pictureBox.Refresh();
                }));
            };

            renderer.Render();

            if (m_stopping)
                return false;

            if (!m_gen_diffs)
                return false;

            var now_filename = Path.GetDirectoryName(a_file.FullName) +
                Path.DirectorySeparatorChar +
                Path.GetFileNameWithoutExtension(a_file.FullName) +
                "_now.png";

            var org_filename = Path.GetDirectoryName(a_file.FullName) +
                Path.DirectorySeparatorChar +
                Path.GetFileNameWithoutExtension(a_file.FullName) +
                "_org.png";

            var diff_filename = Path.GetDirectoryName(a_file.FullName) +
                Path.DirectorySeparatorChar +
                Path.GetFileNameWithoutExtension(a_file.FullName) +
                "_diff.png";

            File.Delete(now_filename);
            File.Delete(diff_filename);
            File.Delete(org_filename);

            ColorArrayFloat now = (ColorArrayFloat)pictureBox.Invoke(new Func<ColorArrayFloat>(() =>
            {
                return new ColorArrayFloat(pictureBox.Image as Bitmap);
            }));
            
            ColorArrayFloatFile org;

            try
            {
                org = new ColorArrayFloatFile(img_file);
            }
            catch
            {
                Logger.Error("Can't load org image: {0}", img_file);
                return false;
            }


            bool err = false;

            if (!org.EqualsContent(now))
            {
                now.GetBitmap().Save(now_filename);
                org.GetBitmap().Save(org_filename);

                ColorArrayFloat diff_area = new ColorArrayFloat(
                    org.Width, org.Height);

                diff_area.Fill(m_errors[0]);

                foreach (var p in diff_area.Pixels())
                {
                    ColorFloat ca = now.GetColor(p.X, p.Y);
                    ColorFloat cb = org.GetColor(p.X, p.Y);
                    Color c1 = ca.SystemColor;
                    Color c2 = cb.SystemColor;

                    int d = Math.Abs(c1.R - c2.R) +
                            Math.Abs(c1.G - c2.G) +
                            Math.Abs(c1.B - c2.B);

                    if (d == 0)
                    {
                        if (ca != cb)
                            d = 1;
                    }

                    ErrorMark(diff_area, p, d);
                }

                diff_area.GetBitmap().Save(diff_filename);

                err = true;
            }

            return err;
        }

        private void ErrorMark(ColorArrayFloat a_array, Point a_p, int a_delta)
        {
            if (a_delta == 0)
                return;

            if (a_delta >= m_errors.Length)
                a_delta = m_errors.Length - 1;

            ColorFloat color = m_errors[a_delta];

            const int RECT_SIZE = 9;

            for (int y = a_p.Y - RECT_SIZE / 2; y <= a_p.Y + RECT_SIZE / 2; y++)
            {
                if (y < 0)
                    continue;
                if (y >= a_array.Height)
                    continue;

                for (int x = a_p.X - RECT_SIZE / 2; x <= a_p.X + RECT_SIZE / 2; x++)
                {
                    if (x < 0)
                        continue;
                    if (x >= a_array.Width)
                        continue;

                    ColorFloat act_color = a_array[x, y];
                    int act_delta = 0;
                    for (int i = 0; i < m_errors.Length; i++)
                    {
                        if (m_errors[i] == act_color)
                        {
                            act_delta = i;
                            break;
                        }
                    }

                    if (act_delta < a_delta)
                        a_array[x, y] = color;
                }
            }
        }

        private void event_testAllErrors_Click(object sender, EventArgs e)
        {
            Logger.Error("Testing all errors.");

            var temp = from file in PathResolver.TestScenesDir.GetFiles("*_area_diff.png")
                       select new
                       {
                           file = new FileInfo(file.FullName.Replace("_area_diff.png", ".xml")),
                           num = file.Name.Split(" - ").First().ToInt()
                       };

            var files = from gr in temp
                        where (Filter == null) || Filter.Contains(gr.num)
                        orderby gr.num
                        select gr.file;

            m_gen_diffs = true;
            TestFiles(files.ToArray());
        }

        private void event_speedTestButton_Click(object sender, EventArgs e)
        {
            Logger.Error("Speed test started.");

            string[] files = new string[]
            {
                "1 - earth_scene.xml",
                "114 - refraction 1-1.5 sphere planes 3.xml",
                "115 - refraction 1-1.5 thin planes.xml"
            };

            files = (from xml in files
                     select PathResolver.TestScenesDir + "\\" + xml).ToArray();

            try
            {
                UpdateButtons(false, false, false);

                Task.Factory.StartNew(() =>
                {
                    m_testing = true;

                    try
                    {
                        int counter = 1;

                        bool canceled = false;

                        foreach (var file in files)
                        {
                            if (m_stopping)
                            {
                                canceled = true;
                                break;
                            }

                            Logger.Info("{0} / {1} - {2}", counter, files.Length,
                                file);

                            Logger.Info("time: {0} [ms]", SpeedTestFile(file));

                            counter++;
                        }

                        if (canceled)
                            Logger.Error("Speed test was cancelled.");
                        else
                            Logger.Error("Speed test ended");

                        m_testing = false;
                        Invoke(new Action(() => Close()));
                    }
                    catch (Exception ex)
                    {
                        Logger.Error(ex);
                    }
                    finally
                    {
                        m_testing = false;
                    }
                });
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                Close();
            }
        }

        private long SpeedTestFile(string a_file)
        {
            Scene scene = Scene.LoadFromFile(a_file);
            scene.RenderOptions.HighPriority = true;

            pictureBox.Invoke(new Action(() =>
            {
                pictureBox.Image = new Bitmap(scene.ActiveCamera.Film.Width,
                    scene.ActiveCamera.Film.Height);
            }));

            Renderer renderer = scene.CreateRenderer();

            renderer.ShouldStop += () => m_stopping;

            renderer.UpdateCallback += () =>
            {
                pictureBox.Invoke(new Action(() =>
                {
                    renderer.UpdateBitmap(pictureBox.Image as Bitmap);
                    pictureBox.Refresh();
                }));
            };

            return Profiler.Profile(() => renderer.Render(), 1, 1, false);
        }

        private void testDebugAllButton_Click(object sender, EventArgs e)
        {
            Logger.Error("Asserting all images.");
            m_gen_diffs = false;
            TestFiles(false);
        }

        private void allowTestsInDebugCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            UpdateButtons(CanTest(), CanAssert(), true);
        }

        private void testDebugSelectedAllButton_Click(object sender, EventArgs e)
        {
            Logger.Error("Asserting selected all.");
            m_gen_diffs = false;
            TestFiles(true);
        }
    }
}